package com.gcloud.controller.network.provider.impl;

import com.gcloud.common.util.StringUtils;
import com.gcloud.controller.ResourceStates;
import com.gcloud.controller.network.dao.NetworkDao;
import com.gcloud.controller.network.dao.RouterDao;
import com.gcloud.controller.network.dao.SubnetDao;
import com.gcloud.controller.network.entity.Network;
import com.gcloud.controller.network.entity.Port;
import com.gcloud.controller.network.entity.Subnet;
import com.gcloud.controller.network.entity.Router;
import com.gcloud.controller.network.model.CreateVSwitchParams;
import com.gcloud.controller.network.model.VSwitchProxyData;
import com.gcloud.controller.network.provider.ISubnetProvider;
import com.gcloud.controller.provider.NeutronProviderProxy;
import com.gcloud.core.exception.GCloudException;
import com.gcloud.core.simpleflow.Flow;
import com.gcloud.core.simpleflow.FlowDoneHandler;
import com.gcloud.core.simpleflow.NoRollbackFlow;
import com.gcloud.core.simpleflow.SimpleFlowChain;
import com.gcloud.header.api.model.CurrentUser;
import com.gcloud.header.enums.ProviderType;
import com.gcloud.header.enums.ResourceType;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class NeutronSubnetProvider implements ISubnetProvider {

    @Autowired
    private NeutronProviderProxy proxy;

    @Autowired
    private SubnetDao subnetDao;
    
    @Autowired
    private NetworkDao networkDao;
    
    @Autowired
    private RouterDao routerDao;

    @Override
    public ResourceType resourceType() {
        return ResourceType.SUBNET;
    }

    @Override
    public ProviderType providerType() {
        return ProviderType.NEUTRON;
    }

    @Override
    public String createSubnet(CreateVSwitchParams params, CurrentUser currentUser) {
    	
    	Network network = networkDao.getById(params.getNetworkId());
    	
    	Router vpc = routerDao.getById(params.getVpcId());
    	
        SimpleFlowChain<VSwitchProxyData, String> chain = new SimpleFlowChain<>("createVSwitch");
        chain.then(new Flow<VSwitchProxyData>("createVSwitch") {
            @Override
            public void run(SimpleFlowChain chain, VSwitchProxyData data) {
            	
            	org.openstack4j.model.network.Network proxyNetwork = null;
            	org.openstack4j.model.network.Subnet proxySubnet = null;
            	
            	if (network != null) {
            		proxySubnet = proxy.createSubnet(network.getProviderRefId(), params.getvSwitchName(), params.getCidrBlock());
        		} else if (vpc != null) {
        			org.openstack4j.model.network.Router proxyRouter = proxy.getRouter(vpc.getProviderRefId());

        			// Create a network
    				String networkId = proxy.createExternalNetwork(String.format("vpcId:%s-network", vpc.getId()));
    				proxyNetwork = proxy.getExternalNetwork(networkId);
    				
    				// Create a subnet
    				proxySubnet = proxy.createSubnet(networkId, vpc.getProviderRefId(), params.getCidrBlock());

    				// Connect the subnet to the router, make the router connect to
    				// the internet.
    				proxy.attachSubnetRouter(proxyRouter.getId(), proxySubnet.getId());
        		}
                VSwitchProxyData vswitch = new VSwitchProxyData();
                vswitch.setNetwork(proxyNetwork);
                vswitch.setSubent(proxySubnet);
                chain.data(vswitch);
                chain.next();
            }

            @Override
            public void rollback(SimpleFlowChain chain, VSwitchProxyData data) {
            	org.openstack4j.model.network.Network network = data.getNetwork();
            	org.openstack4j.model.network.Subnet subnet = data.getSubent();
                if (StringUtils.isNotBlank(params.getNetworkId())) {
                	proxy.deleteSubnet(subnet.getId());
        		} else if (StringUtils.isNotBlank(params.getVpcId())) {
        			org.openstack4j.model.network.Router router = proxy.getRouter(vpc.getProviderRefId());
    				proxy.detachSubnetRouter(router.getId(), subnet.getId());
    				proxy.deleteSubnet(subnet.getId());
    				proxy.deleteNetwork(network.getId());	
        		}
            }

        }).then(new NoRollbackFlow<VSwitchProxyData>("save to db") {
            @Override
            public void run(SimpleFlowChain chain, VSwitchProxyData data) {
            	org.openstack4j.model.network.Network proxyNetwork = data.getNetwork();
            	org.openstack4j.model.network.Subnet proxySubnet = data.getSubent();
            	
                Subnet newSubnet = new Subnet();
                newSubnet.setId(UUID.randomUUID().toString());
                newSubnet.setCidr(params.getCidrBlock());
                newSubnet.setName(params.getvSwitchName());
                newSubnet.setUserId(currentUser.getId());
                newSubnet.setCreateTime(new Date());
                newSubnet.setNetworkId(params.getVpcId());
                newSubnet.setZoneId(params.getRegionId());
                newSubnet.setProvider(providerType().getValue());
                newSubnet.setProviderRefId(proxySubnet.getId());
                newSubnet.setTenantId(currentUser.getDefaultTenant());
                newSubnet.setCreateTime(new Date());
                subnetDao.save(newSubnet);
            	if (vpc != null) {
	                Network newNetwork = new Network();
	                newNetwork.setId(UUID.randomUUID().toString());
	                newNetwork.setName(proxyNetwork.getName());
	                newNetwork.setProvider(providerType().getValue());
	                newNetwork.setProviderRefId(proxyNetwork.getId());
	                newNetwork.setTenantId(currentUser.getDefaultTenant());
	                newNetwork.setRegionId(params.getRegionId());
	                newNetwork.setCreateTime(new Date());
	                network.setStatus(ResourceStates.status(ResourceType.ROUTER, ProviderType.NEUTRON, proxyNetwork.getStatus().name()));
	                networkDao.save(newNetwork);
            	}

                chain.next();
            }
        }).done(new FlowDoneHandler<VSwitchProxyData>() {
            @Override
            public void handle(VSwitchProxyData data) {
                chain.setResult(data.getSubent().getId());
            }
        }).start();

        if (StringUtils.isNotBlank(chain.getErrorCode())) {
            throw new GCloudException(chain.getErrorCode());
        }
       
        return chain.data().getSubent().getId();

    }

    @Override
    public void deleteSubnet(String subnetRefId) {
        proxy.deleteSubnet(subnetRefId);
    }

    @Override
    public void modifyAttribute(String subnetRefId, String subnetName) {
        proxy.modifySubnetAttribute(subnetRefId, subnetName);
    }

    @Override
    public List<Subnet> list(Map<String, String> filter) {
        List<org.openstack4j.model.network.Subnet> subnets = proxy.listSubnet(filter);
        List<Subnet> retList = new ArrayList<>();
        for (org.openstack4j.model.network.Subnet s : subnets) {
            Subnet sn = new Subnet();
            sn.setCidr(s.getCidr());
            sn.setId(s.getId());
            sn.setName(s.getName());
            sn.setNetworkId(s.getNetworkId());
//            sn.setRouterId();
            sn.setUpdatedAt(s.getUpdatedAt());
            // TODO: other items.

            retList.add(sn);
        }

        return retList;
    }
}
